﻿using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Linq;
using System.Reflection;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using System.IO;
using Helpers.Extensions;
using HtmlAgilityPack;

namespace Helpers
{
    public static class HttpClientExt
    {
        private static Random s_random = new Random();

        #region httpclient依赖注入
        public static IServiceCollection InjectHttpClient(this IServiceCollection services, string dynamicProxy, string dynamicOverWallProxy, string staticOverWallProxy)
        {
            Console.WriteLine($"动态代理:{dynamicProxy}");
            Console.WriteLine($"动态翻墙代理:{dynamicOverWallProxy}");
            Console.WriteLine($"静态翻墙代理:{staticOverWallProxy}");
            return InjectHttpClient(services, new WebProxy(dynamicProxy), new WebProxy(dynamicOverWallProxy), new WebProxy(staticOverWallProxy));
        }

        /// <summary>
        /// proxy,noproxy,smdo,simple
        /// proxy:使用代理,带User-Agent;
        /// noproxy:不适用代理,带User-Agent;
        /// smdo:调用smdo webapi;
        /// simple:默认,可以使用cookie,不带User-Agent;
        /// </summary>
        /// <param name="services">服务</param>
        /// <param name="dynamicProxy">代理</param>
        public static IServiceCollection InjectHttpClient(this IServiceCollection services, IWebProxy dynamicProxy, IWebProxy dynamicOverWallProxy, IWebProxy staticOverWallProxy)
        {
            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.proxy_noRedirect_noCookies.ToString(), client =>
            {
                ConfigureHttpClient(client);
            })
           .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
           {
               AllowAutoRedirect = false,
               UseCookies = false,
               AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
               Proxy = dynamicProxy,
           })
            .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.noProxy_noRedirect_noCookies.ToString(), client =>
            {
                ConfigureHttpClient(client);
            })
           .ConfigurePrimaryHttpMessageHandler(() =>
           {
               return new HttpClientHandler
               {
                   AllowAutoRedirect = false,
                   UseCookies = false,
                   AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                   Proxy = null,
               };
           })
           .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.noProxy_Redirect_Cookies.ToString(), client =>
            {
                ConfigureHttpClient(client);
            })
          .ConfigurePrimaryHttpMessageHandler(() =>
          {
              return new HttpClientHandler
              {
                  AllowAutoRedirect = true,
                  UseCookies = false,
                  AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                  Proxy = new WebProxy("127.0.0.1:8888"),
              };
          })
          .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.dynamicOverWall.ToString(), client =>
            {
                ConfigureHttpClient(client);
            })
            .ConfigurePrimaryHttpMessageHandler(() =>
            {
                return new HttpClientHandler
                {
                    AllowAutoRedirect = false,
                    UseCookies = true,
                    AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                    Proxy = dynamicOverWallProxy,
                };
            })
           .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.staticOverWall.ToString(), client =>
            {
                ConfigureHttpClient(client);
            })
            .ConfigurePrimaryHttpMessageHandler(() =>
            {
                return new HttpClientHandler
                {
                    AllowAutoRedirect = false,
                    UseCookies = true,
                    AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                    Proxy = staticOverWallProxy,
                };
            })
            .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(HttpClientHelper.HttpClientNameEnum.smdo.ToString(), client =>
            {
                ConfigureHttpClient(client);
                client.DefaultRequestHeaders.Add("userAgent", "smartdo.scc");
            })
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
            {
                AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                UseCookies = false,
            })
            .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);

            services.AddHttpClient(string.Empty, client =>
            {
                ConfigureHttpClient(client);
            })
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
            {
                AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip,
                UseCookies = false,
            })
            .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan);
            return services;
        }

        private static void ConfigureHttpClient(HttpClient client)
        {
            client.Timeout = TimeSpan.FromSeconds(60);
            client.DefaultRequestHeaders.Add("Accept", "text/html, application/xhtml+xml, image/jxr, */*");
            client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate");
            client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4");
        }
        #endregion

        #region 分析网页的常用方法,流式写法
        /// <summary>
        /// 随机得到数组中的一个值
        /// </summary>
        public static T GetRandomValue<T>(this IEnumerable<T> @this)
        {
            if (@this == null || @this.Count() == 0)
                return default(T);
            return @this.ElementAt(s_random.Next(@this.Count()));
        }

        public static StringContent FormUrlEncodedContent(this Dictionary<string, string> keyValuePairs, Encoding encoding = null)
        {
            //FormUrlEncodedContent对参数有长度限制
            var list = keyValuePairs.Select(it => $"{it.Key}={it.Value.UrlEncode()}");
            return new StringContent(string.Join("&", list), encoding ?? Encoding.UTF8, "application/x-www-form-urlencoded");
        }

        /// <summary>
        /// 将实体转换成get请求的参数,去掉null值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="this"></param>
        /// <returns></returns>
        public static string ToUrlParams(this Object @this)
        {
            if (@this == null)
                return string.Empty;
            Type type = @this.GetType();
            var list = type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
                ?.Select(pi =>
                {
                    var value = pi.GetValue(@this, null)?.ToString();
                    //Console.WriteLine($"{pi.Name} {value}");
                    return $"{pi.Name}={value.UrlEncode()}";
                })
                .Where(it => it.Last() != '=');//去掉null值
            return string.Join("&", list);
        }
        #endregion

        #region httpclient扩展方法
        public static void AddRandomChromeUserAgent(this HttpClient client)
        {
            client.DefaultRequestHeaders.Add(HttpClientHelper.s_userAgent, CrawlerHelper.GetRandomChromeUserAgent());
        }
        public static HttpClient CreateClient(this IHttpClientFactory factory, HttpClientHelper.HttpClientNameEnum httpClientName)
        {
            return factory.CreateClient(httpClientName.ToString());
        }

        public static Task<T> PutAsJsonAndReadAs<T>(this HttpClient clien, string requestUri, object value, Encoding encoding = default)
            where T : class
            => ReadHttpResponseMessageAs<T>(() => clien.PutAsJsonAsync(requestUri, value), encoding);

        public static Task<T> DeleteAndReadAsAsync<T>(this HttpClient client, string url, Encoding encoding = default)
           where T : class => ReadHttpResponseMessageAs<T>(() => client.DeleteAsync(url), encoding);
        public static Task<string> GetStringAsync(this HttpClient client, HttpRequestMessage hrm, Encoding encoding = default)
         => ReadHttpResponseMessageAs<string>(() => client.SendAsync(hrm), encoding);
        public static Task<T> GetJsonAsync<T>(this HttpClient client, string url, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.GetAsync(url), encoding);
        public static Task<T> GetJsonAsync<T>(this HttpClient client, HttpRequestMessage hrm, Encoding encoding = default)
          where T : class => ReadHttpResponseMessageAs<T>(() => client.SendAsync(hrm), encoding);

        public static Task<T> GetAndReadAsAsync<T>(this HttpClient client, string url, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.GetAsync(url), encoding);
        public static Task<T> GetAndReadAsAsync<T>(this HttpClient client, string url, object request, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.GetAsync($"{url}?{request.ToUrlParams()}"), encoding);
        public static Task<T> GetAndReadAsAnonymousTypeAsync<T>(this HttpClient client, string url, T obj, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.GetAsync(url), encoding);
        public static Task<T> GetAndReadAsAnonymousTypeAsync<T>(this HttpClient client, string url, object request, T obj, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.GetAsync($"{url}?{request.ToUrlParams()}"), encoding);

        public static Task<T> PostAsFormUrlEncodedAndReadAsAnonymousTypeAsync<T>(this HttpClient client, T response, string url, Dictionary<string, string> keyValuePairs, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.PostAsync(url, FormUrlEncodedContent(keyValuePairs, encoding)), encoding);
        public static Task<T> PostAsFormUrlEncodedAsync<T>(this HttpClient client, string url, Dictionary<string, string> keyValuePairs, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(() => client.PostAsync(url, FormUrlEncodedContent(keyValuePairs, encoding)), encoding);

        public static Task<T> PostAsFormDataAsync<T>(this HttpClient client, string url, string boundary, Dictionary<string, string> keyValuePairs, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(async () =>
                                         {
                                             encoding = encoding ?? Encoding.UTF8;
                                             var formData = new MultipartFormDataContent(boundary);
                                             foreach (var item in keyValuePairs)
                                             {
                                                 ByteArrayContent txtContent = new ByteArrayContent(encoding.GetBytes(item.Value));
                                                 txtContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
                                                 {
                                                     Name = item.Key
                                                 };
                                                 formData.Add(txtContent);
                                             }
                                             var response = await client.PostAsync(url, formData);
                                             return response;
                                         }, encoding);
        public static Task<T> PostAsFormDataAndReadAsAnonymousTypeAsync<T>(this HttpClient client, T obj, string url, string boundary, Dictionary<string, string> keyValuePairs, Encoding encoding = default)
            where T : class => ReadHttpResponseMessageAs<T>(async () =>
                                         {
                                             encoding = encoding ?? Encoding.UTF8;
                                             var formData = new MultipartFormDataContent(boundary);
                                             foreach (var item in keyValuePairs)
                                             {
                                                 ByteArrayContent txtContent = new ByteArrayContent(encoding.GetBytes(item.Value));
                                                 txtContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
                                                 {
                                                     Name = item.Key
                                                 };
                                                 formData.Add(txtContent);
                                             }
                                             var response = await client.PostAsync(url, formData);
                                             return response;
                                         }, encoding);

        public static Task<T> PostAsJsonAndReadAs<T>(this HttpClient clien, string requestUri, object value, Encoding encoding = default)
            where T : class
            => ReadHttpResponseMessageAs<T>(() => clien.PostAsJsonAsync(requestUri, value), encoding);

        private static async Task<T> ReadHttpResponseMessageAs<T>(Func<Task<HttpResponseMessage>> func, Encoding encoding = default)
            where T : class
        {
            string content = null;
            HttpResponseMessage response = null;
            try
            {
                response = await func();
                //先读取body,再判断状态,这样可以在异常中返回错误的内容
                var stream = await response.Content.ReadAsStreamAsync();
                using (StreamReader sr = new StreamReader(stream, encoding ?? Encoding.UTF8))
                {
                    content = await sr.ReadToEndAsync();
                }
                response.EnsureSuccessStatusCode();
            }
            catch (Exception)
            {
                if (content != null && content != null)
                    throw new HttpRequestException($"{(int)response.StatusCode}({response.StatusCode})-{content}");
                else
                    throw;
            }
            if (typeof(T) == typeof(string))
                return content as T;
            else
                return JsonConvert.DeserializeObject<T>(content);

        }

        #endregion


        #region HttpResponseMessage
        public static async Task<T> ReadAsJsonAsync<T>(this HttpResponseMessage response)
        {
            var content = await response.Content.ReadAsStringAsync();
            var json = JsonConvert.DeserializeObject<T>(content);
            return json;
        }

        public static async Task<HtmlNode> ReadAsDocumentNodeAsync(this HttpResponseMessage response)
        {
            var content = await response.Content.ReadAsStringAsync();
            HtmlDocument htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(content);
            return htmlDocument.DocumentNode;
        }

        #endregion
    }
}

